#ifndef __BASIC_MAYBY_v2_HH__
#define __BASIC_MAYBY_v2_HH__

#include <cctype>
#include <cstring>
#include <fstream>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <stdexcept>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
//#include <typeinfo>
#include <boost/optional.hpp>
#include <boost/ptr_container/ptr_list.hpp>
#include <boost/algorithm/string.hpp>

using namespace std;
using boost::optional;

enum keyword_e {
  K_CLR,
  K_DATA,
  K_END,
  K_FOR,
  K_GOSUB,
  K_GOTO,
  K_IF,
  K_INPUT,
  K_LET,
  K_LIST,
  K_NEXT,
  K_ON,
  K_PEEK,
  K_POKE,
  K_PRINT,
  K_PROC,
  K_READ,
  K_REM,
  K_RESTORE,
  K_RETURN,
  K_RUN,
  K_STEP,
  K_THEN,
  K_TO,
  KF_NUMERIC_FUNCTIONS,
  F_INT,
  F_SIN,
  F_COS,
  F_DEC,
  KF_STRING_FUNCTIONS,
  F_STR,
  F_HEX,
  F_MID,
  K_END_OF_KEYWORDS
};

/*! \brief Map of keywords to a number
 *
 * This type is used to map all known keywords to a number so that we
 * can use the swich statement later on.
 */
typedef map<string,int> KeywordMapType;
extern KeywordMapType default_keywords;

class Token {
public:
  virtual ~Token() {}
  virtual string str() const = 0;
};
typedef const Token * TokenPtr; //Is a shared_ptr really necessary?
typedef std::list<TokenPtr> TokenListType;

class TokNumeric : public Token {
public:
  virtual float as_float() const = 0;
};

class TokNum : public TokNumeric {
public:
  int n;
  TokNum(int x);
  TokNum(const string &x);
  virtual string str() const;
  virtual float as_float() const;
};

class TokFloat : public TokNumeric {
public:
  float n;
  TokFloat(float x);
  TokFloat(const string &x);
  virtual string str() const;
  virtual float as_float() const;
};

class TokString : public Token {
public:
  string s;
  TokString(const string &x);
  virtual string str() const;
};

class TokIdentifier : public Token {};
class TokCmd : public TokIdentifier {
protected:
  string identifier;

public:
  int cmd;

  TokCmd(const KeywordMapType &keywords, const string &x);
  TokCmd(keyword_e x);
  virtual string str() const;
};

class TokVar : public TokIdentifier {
public:
  string var;
  TokVar(const string &x);
  virtual string str() const;
};
class TokIntVar : public TokVar { public: TokIntVar(const string &x) : TokVar(x) {}; };
class TokFloatVar : public TokVar { public: TokFloatVar(const string &x) : TokVar(x) {}; };
class TokStringVar : public TokVar { public: TokStringVar(const string &x) : TokVar(x) {}; };

class TokOperators : public Token {
public:
  char op;
  TokOperators(char x);
  virtual string str() const;
};

class TokEqual : public TokOperators { public: TokEqual() : TokOperators('=') {} };
class TokOp : public TokOperators { public: TokOp(char x) : TokOperators(x) {} };
class TokRelOp : public TokOperators {
public:
  TokRelOp(char x);
  virtual string str() const;
};

class TokPar : public Token {
public:
  char par;
  TokPar(char x);
  virtual string str() const;
};
class TokParOpen : public TokPar { public: TokParOpen(char c) : TokPar(c) {} };
class TokParClose : public TokPar { public: TokParClose(char c) : TokPar(c) {} };

class TokSep : public Token {
public:
  char sep;
  TokSep(char x);
  virtual string str() const;
};

TokenListType tokenise(const KeywordMapType &keywords, const string &line);

class Basic {
  /*
   * GRAMMAR
   * =======
   *
   * statement: command | assignment
   * assignment: (LET) variable "=" numexpr
   * numexpr: term | term op numexpr
   * term: var | constant | "(" numexpr ")"
   */
public:
  typedef int IntegerType;
  typedef float FloatType;
  typedef unordered_map<string,IntegerType> IntVarMapType;
  typedef unordered_map<string,FloatType> VarMapType;
  typedef unordered_map<string,string> StringVarMapType;

  class Error : public std::exception {};
  class OperatorMissingError : public Error { public: virtual const char *what() const throw() { return "operator missing"; } };
  class OperandMissingError : public Error { public: virtual const char *what() const throw() { return "operand missing"; } };
  class NumericValueExpectedError : public Error { public: virtual const char *what() const throw() { return "numerical value expected"; } };
  class VariableExpectedError : public Error { public: virtual const char *what() const throw() { return "variable expected"; } };
  class MissingTermError : public Error { public: virtual const char *what() const throw() { return "term missing"; } };
  class UndefinedVariableError : public Error { public: virtual const char *what() const throw() { return "undefined variable"; } };
  class MalformedRelationError : public Error { public: virtual const char *what() const throw() { return "malformed relation"; } };
  class UndefinedStatementError : public Error { public: virtual const char *what() const throw() { return "undefined statement"; } };
  class StackUnderflowError : public Error { public: virtual const char *what() const throw() { return "stack underflow"; } };
  class SyntaxError : public Error { public: virtual const char *what() const throw() { return "syntax error"; } };
  class UnbalancedParenthesisError : public Error { public: virtual const char *what() const throw() { return "unbalanced parenthesis"; } };
  class NextWithoutForError : public Error { public: virtual const char *what() const throw() { return "next without for"; } };
  class MismatchedVariableError : public Error { public: virtual const char *what() const throw() { return "mismatched variable"; } };
  class TypeMismatchError : public Error { public: virtual const char *what() const throw() { return "type mismatch"; } };
  class IllegalQuantityError : public Error { public: virtual const char *what() const throw() { return "illegal quantity"; } };
  class OutOfDataError : public Error { public: virtual const char *what() const throw() { return "out of data"; } };


protected:
  IntVarMapType varsi;
  VarMapType varsf;
  StringVarMapType varss;
  TokCmd default_goto_token;
  map<unsigned short, TokenListType >::const_iterator curline;
  map<unsigned short, TokenListType >::const_iterator dataline;
  TokenListType::const_iterator datatoken;
  TokenListType::const_iterator beg;
  TokenListType::const_iterator end;
  struct CallStackElement {
    map<unsigned short,TokenListType>::const_iterator lineno;
    CallStackElement(const map<unsigned short,TokenListType>::const_iterator &x) : lineno(x) {}
    virtual ~CallStackElement() {}
  };
  struct CallStackFornext : CallStackElement {
    string variable;
    float increment;
    float to;
    CallStackFornext(const map<unsigned short,TokenListType>::const_iterator &l, const string &x, float y, float z) : CallStackElement(l), variable(x), increment(y), to(z) {}
  };
  struct CallStackFornextI : CallStackElement {
    string variable;
    int increment;
    int to;
    CallStackFornextI(const map<unsigned short,TokenListType>::const_iterator &l, const string &x, int y, int z) : CallStackElement(l), variable(x), increment(y), to(z) {}
  };
  typedef boost::ptr_list<CallStackElement> stack_t;
  stack_t call_stack;

  bool running() const;

  FloatType parenthesis();
  FloatType term();
  FloatType numexpr();
  FloatType numexpr2();
  FloatType numexpr3(FloatType x);
  bool relation();
  string exec_strfun(int cmd);
  string exec_mid();
  /* \brief string expression
   *
   * strexpr: String | String * term | String + strexpr | numexpr | strfunction ( strexpr )
   */
  string strexpr();
  FloatType exec_funcall();
  void exec_assignment();
  void exec_for();
  void exec_next();
  void exec_run();
  void exec_run(const map<unsigned short,TokenListType>::const_iterator &start_with, bool rescan_procedures);
  void internal_jump_to_line(unsigned short linenum, bool push_stack);
  void exec_gosub();
  void exec_goto();
  void exec_on();
  virtual FloatType exec_peek();
  virtual void exec_poke();
  virtual void exec_poke2(FloatType addr, FloatType val);
  void exec_return();
  const TokCmd *exec_ifthen();
  void exec_list();
  void exec_clear();
  void exec_new();
  const Token *internal_read(bool need_sep);
  void exec_print();
  void exec_read();
  void exec_restore();
  const TokCmd *statement();
  /*! \brief called when command is unknown
   *
   * If you ever need to extend the basic overwrite this virtual
   * function.
   */
  virtual void unknown_command() { throw SyntaxError(); }

public:
  map<unsigned short,TokenListType> prg;

  Basic();
  ~Basic();

  /*! \brief Add a line to the program.
   *
   * This takes care of the fact that if a line is added twice the old
   * line has to be overwritten. And the old memory freed, of course.
   */
  void add_line(unsigned short line, TokenListType &code);

  /*! \brief Run program starting from first line
   */
  virtual void run();

  /*! \brief run program starting from line line
   */
  virtual void run(unsigned short line);

  unsigned long numvars() const;
  const Token *get_current_token() const;
  unsigned short get_current_line_no() const {return curline->first;}
  const TokCmd *statement(TokenListType::const_iterator beg_, TokenListType::const_iterator end_);
  
  IntegerType &var_int(string name) { name += '%'; return varsi[name]; }
  FloatType &var_float(const string &name) { return varsf[name]; }
  string &var_string(string name) { name += '$'; return varss[name]; }
};
#endif
